﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;

namespace Utilities
{
    public class EncryptionProvider
    {
        //Create by padding secific key
        static private byte[] CreateKey(byte[] key, int size)
        {
            byte[] bytes = new byte[size / 8];
            key.CopyTo(bytes, 0);
            return bytes;
        }

        //Rjindael Encryption
        static public byte[] AesEncrypt(byte[] key, byte[] data, int size)
        {
            byte[] cipher = null;
            RijndaelManaged rijndael = new RijndaelManaged();

            byte[] padKey = CreateKey(key, size);

            rijndael.Key = padKey;
            rijndael.IV = padKey;
            rijndael.Padding = PaddingMode.PKCS7;

            // use this stream to store result
            MemoryStream outStream = new MemoryStream();
            CryptoStream stream = null;
            try
            {
                ICryptoTransform transform = rijndael.CreateEncryptor(padKey, padKey);
                stream = new CryptoStream(outStream, transform, CryptoStreamMode.Write);

                // writing data
                stream.Write(data, 0, data.Length);
                stream.FlushFinalBlock();

                cipher = outStream.ToArray();
                rijndael.Clear();
            }
            catch (Exception)
            {

                throw;
            }
            finally
            {
                outStream.Close();
                stream.Close();
            }
            return cipher;
        }

        //TripleDes Encryption
        static public byte[] TripleDesEncrypt(byte[] data, byte[] key, int size)
        {
            byte[] cipher = null;
            TripleDESCryptoServiceProvider tdesCsp = new TripleDESCryptoServiceProvider();

            byte[] padKey = CreateKey(key, size);

            tdesCsp.Key = padKey;
            tdesCsp.IV = new byte[tdesCsp.BlockSize / 8];
            tdesCsp.Padding = PaddingMode.PKCS7;

            // use this stream to store result
            MemoryStream outStream = new MemoryStream();
            CryptoStream stream = null;
            try
            {
                ICryptoTransform transform = tdesCsp.CreateEncryptor(padKey, padKey);
                stream = new CryptoStream(outStream, transform, CryptoStreamMode.Write);

                // writing data
                stream.Write(data, 0, data.Length);
                stream.FlushFinalBlock();

                cipher = outStream.ToArray();
                tdesCsp.Clear();
            }
            catch (Exception)
            {

                throw;
            }
            finally
            {
                outStream.Close();
                stream.Close();
            }
            return cipher;
        }

        //TripleDes Description
        static public byte[] TripleDesDecrypt(byte[] data, byte[] key, int size)
        {
            byte[] decrypt = null;
            TripleDESCryptoServiceProvider tdesCsp = new TripleDESCryptoServiceProvider();

            byte[] padKey = CreateKey(key, size);

            tdesCsp.Key = padKey;
            tdesCsp.IV = new byte[tdesCsp.BlockSize / 8];
            tdesCsp.Padding = PaddingMode.PKCS7;

            // use this stream to store result
            MemoryStream outStream = new MemoryStream();
            CryptoStream stream = null;
            try
            {
                ICryptoTransform transform = tdesCsp.CreateDecryptor(padKey, padKey);
                stream = new CryptoStream(outStream, transform, CryptoStreamMode.Write);

                // writing data
                stream.Write(data, 0, data.Length);
                stream.FlushFinalBlock();

                decrypt = outStream.ToArray();
                tdesCsp.Clear();
            }
            catch (Exception)
            {

                throw;
            }
            finally
            {
                outStream.Close();
                stream.Close();
            }
            return decrypt;
        }

        //Hash data with hash algorithm
        static public byte[] Hash(byte[] data, HashAlgorithm algo)
        {
            try
            {
                return algo.ComputeHash(data);
            }
            catch (Exception)
            {

                throw;
            }
        }

        //Encryption with symmetric algorithm
        static public byte[] Encrypt(byte[] data, SymmetricAlgorithm encAlgo, byte[] key, PaddingMode padding, CipherMode mop)
        {
            byte[] encrypt = null;

            encAlgo.Key = key;
            encAlgo.IV = new byte[encAlgo.BlockSize / 8];
            encAlgo.Padding = padding;
            encAlgo.Mode = mop;

            // use this stream to store result
            MemoryStream outStream = new MemoryStream();
            CryptoStream stream = null;
            try
            {
                ICryptoTransform transform = encAlgo.CreateEncryptor(encAlgo.Key, encAlgo.IV);
                stream = new CryptoStream(outStream, transform, CryptoStreamMode.Write);

                // writing data
                stream.Write(data, 0, data.Length);
                stream.FlushFinalBlock();

                encrypt = outStream.ToArray();
                encAlgo.Clear();
            }
            catch (Exception)
            {

                throw;
            }
            finally
            {
                outStream.Close();
                stream.Close();
            }
            return encrypt;
        }

        static public byte[] Encrypt(byte[] data, SymmetricAlgorithm encAlgo, byte[] key)
        {
            return Encrypt(data, encAlgo, key, PaddingMode.None, CipherMode.CBC);
        }

        //Description with symmetric algorithm
        static public byte[] Decrypt(byte[] data, SymmetricAlgorithm decAlgo, byte[] key, PaddingMode padding, CipherMode mop)
        {
            byte[] decrypt = null;

            decAlgo.Key = key;
            decAlgo.IV = new byte[decAlgo.BlockSize / 8];
            decAlgo.Padding = padding;
            decAlgo.Mode = mop;

            // use this stream to store result
            MemoryStream outStream = new MemoryStream();
            CryptoStream stream = null;
            try
            {
                ICryptoTransform transform = decAlgo.CreateDecryptor(decAlgo.Key, decAlgo.IV);
                stream = new CryptoStream(outStream, transform, CryptoStreamMode.Write);

                // writing data
                stream.Write(data, 0, data.Length);
                stream.FlushFinalBlock();

                decrypt = outStream.ToArray();
                decAlgo.Clear();
            }
            catch (Exception)
            {

                throw;
            }
            finally
            {
                outStream.Close();
                stream.Close();
            }
            return decrypt;
        }

        static public byte[] Decrypt(byte[] data, SymmetricAlgorithm decAlgo, byte[] key)
        {
            return Decrypt(data, decAlgo, key, PaddingMode.None, CipherMode.CBC);
        }

        public static string Encrypt(SymmetricAlgorithm saCryptoProvider, string sPlaintext, byte[] arrKey, byte[] arrIV, PaddingMode pAdding, CipherMode mode)
        {
            saCryptoProvider.Mode = mode;
            saCryptoProvider.Padding = pAdding;

            MemoryStream memoryStream = new MemoryStream();

            CryptoStream cryptoStream = new CryptoStream(memoryStream, saCryptoProvider.CreateEncryptor(arrKey, arrIV), CryptoStreamMode.Write);
            StreamWriter writer = new StreamWriter(cryptoStream);

            writer.Write(sPlaintext);
            writer.Flush();
            cryptoStream.FlushFinalBlock();
            writer.Flush();

            return Convert.ToBase64String(memoryStream.GetBuffer(), 0, (int)memoryStream.Length);
        }

        public static string Decrypt(SymmetricAlgorithm saCryptoProvider, string sCypherText, byte[] arrKey, byte[] arrIV, PaddingMode pAdding, CipherMode mode)
        {
            saCryptoProvider.Padding = pAdding;
            saCryptoProvider.Mode = mode;
            MemoryStream memoryStream = new MemoryStream(Convert.FromBase64String(sCypherText));
            CryptoStream cryptoStream = new CryptoStream(memoryStream, saCryptoProvider.CreateDecryptor(arrKey, arrIV), CryptoStreamMode.Read);
            StreamReader reader = new StreamReader(cryptoStream);

            return reader.ReadToEnd();
        }

        //Hash and Sign
        public byte[] Sign(byte[] data, RSACryptoServiceProvider rsa, HashAlgorithm hashAlgo)
        {
            try
            {
                byte[] hashBytes = Hash(data, hashAlgo);
                return rsa.SignData(hashBytes, hashAlgo);
            }
            catch (Exception)
            {

                throw;
            }
        }

        //Sign and Close Streams
        static public byte[] Sign(Stream inStream, Stream outStream, RSACryptoServiceProvider rsa, HashAlgorithm hashAlgo)
        {
            try
            {
                byte[] hash = hashAlgo.ComputeHash(inStream);
                byte[] sign = rsa.SignData(hash, hashAlgo);
                outStream.Write(sign, 0, sign.Length);
                return sign;
            }
            catch (Exception)
            {

                throw;
            }
            finally
            {
                inStream.Close();
                outStream.Close();
                rsa.Clear();
            }
        }

        //Verify data and close input Streams
        static public bool Verify(Stream inStream, Stream signStream, RSACryptoServiceProvider rsa, HashAlgorithm hashAlgo)
        {
            try
            {
                byte[] hash = hashAlgo.ComputeHash(inStream);
                byte[] sign = new byte[signStream.Length];
                signStream.Read(sign, 0, sign.Length);
                return rsa.VerifyData(hash, hashAlgo, sign);
            }
            catch (Exception)
            {

                throw;
            }
            finally
            {
                signStream.Close();
                inStream.Close();
                rsa.Clear();
            }
        }

        //Hash and verify signature
        public bool Verify(byte[] data, RSACryptoServiceProvider rsa, HashAlgorithm hashAlgo, byte[] signature)
        {
            try
            {
                return rsa.VerifyData(data, hashAlgo, signature);
            }
            catch (Exception)
            {

                throw;
            }
        }

        //Generate key bytes based on symmetric encryption algorithm
        static public byte[] GenerateKeyBytes(SymmetricAlgorithm encAlgo)
        {
            byte[] keyBytes = null;
            int size = 0;
            size = encAlgo.KeySize / 8;

            keyBytes = new byte[size];
            RNGCryptoServiceProvider rngCsp = new RNGCryptoServiceProvider();
            rngCsp.GetBytes(keyBytes);
            encAlgo.Clear();

            return keyBytes;
        }

        //Encrypt file and close streams
        static public void Encrypt(Stream inStream, Stream outStream, SymmetricAlgorithm encAlgo, byte[] key, RSACryptoServiceProvider rsa, PaddingMode padding, CipherMode mop)
        {
            try
            {
                byte[] block = new byte[encAlgo.BlockSize / 8];

                // encrypt key
                byte[] encKey = rsa.Encrypt(key, false);


                encAlgo.Key = key;
                encAlgo.IV = new byte[encAlgo.BlockSize / 8];
                encAlgo.Padding = padding;
                encAlgo.Mode = mop;

                CryptoStream stream = null;
                try
                {
                    ICryptoTransform transform = encAlgo.CreateEncryptor(encAlgo.Key, encAlgo.IV);
                    stream = new CryptoStream(outStream, transform, CryptoStreamMode.Write);

                    while (inStream.Read(block, 0, block.Length) > 0)
                    {
                        stream.Write(block, 0, block.Length);
                    }

                    stream.FlushFinalBlock();
                    // write thong tin ma hoa vao cuoi file
                    // key
                    outStream.WriteByte((byte)encKey.Length);
                    outStream.Write(encKey, 0, encKey.Length);

                    // ma hoa
                    //char[] caEncAlgoName = encAlgo.GetType().Name.ToCharArray();
                    char[] caEncAlgoName = GetSymmetricAlgoName(encAlgo).ToCharArray();
                    outStream.WriteByte((byte)caEncAlgoName.Length);
                    outStream.Write(Encoding.ASCII.GetBytes(caEncAlgoName), 0, caEncAlgoName.Length);

                    // padding mode
                    char[] caPaddingMode = Enum.GetName(typeof(PaddingMode), padding).ToCharArray();
                    outStream.WriteByte((byte)caPaddingMode.Length);
                    outStream.Write(Encoding.ASCII.GetBytes(caPaddingMode), 0, caPaddingMode.Length);

                    // cipher mode
                    char[] caCipherMode = Enum.GetName(typeof(CipherMode), mop).ToCharArray();
                    outStream.WriteByte((byte)caCipherMode.Length);
                    outStream.Write(Encoding.ASCII.GetBytes(caCipherMode), 0, caCipherMode.Length);

                    // write length modifier
                    int len =
                        1 + encKey.Length +
                        1 + caEncAlgoName.Length +
                        1 + caPaddingMode.Length +
                        1 + caCipherMode.Length;

                    outStream.WriteByte((byte)len);

                    outStream.Flush();
                }
                catch (Exception)
                {
                    throw;
                }
                finally
                {
                    encAlgo.Clear();
                    stream.Close();
                }
            }
            catch (Exception)
            {
                throw;
            }
            finally
            {
                inStream.Close();
                outStream.Close();
            }
        }

        //Descript file and close streams
        static public void Decrypt(Stream inStream, Stream outStream, RSACryptoServiceProvider rsa)
        {
            try
            {
                inStream.Seek(-1, SeekOrigin.End);

                int len = (int)inStream.ReadByte();
                inStream.Seek(-len - 1, SeekOrigin.Current);

                // doc key
                int keyLen = (int)inStream.ReadByte();
                byte[] encKey = new byte[keyLen];
                inStream.Read(encKey, 0, encKey.Length);
                // decrypt key
                byte[] key = rsa.Decrypt(encKey, false);


                // doc thuat toan ma hoa
                int algoLen = (int)inStream.ReadByte();
                byte[] algo = new byte[algoLen];
                inStream.Read(algo, 0, algo.Length);
                string algoName = Encoding.ASCII.GetString(algo);
                SymmetricAlgorithm encAlgo = SymmetricAlgorithm.Create(algoName);

                // doc padding mode
                int pLen = (int)inStream.ReadByte();
                byte[] padding = new byte[pLen];
                inStream.Read(padding, 0, padding.Length);
                string paddingName = Encoding.ASCII.GetString(padding);
                PaddingMode paddingMode = (PaddingMode)Enum.Parse(typeof(PaddingMode), paddingName);

                // doc cipher mode
                int cLen = (int)inStream.ReadByte();
                byte[] cipher = new byte[cLen];
                inStream.Read(cipher, 0, cipher.Length);
                string cipherName = Encoding.ASCII.GetString(cipher);
                CipherMode cipherMode = (CipherMode)Enum.Parse(typeof(CipherMode), cipherName);

                byte[] block = new byte[encAlgo.BlockSize / 8];

                encAlgo.Key = key;
                encAlgo.IV = new byte[encAlgo.BlockSize / 8];
                encAlgo.Padding = paddingMode;
                encAlgo.Mode = cipherMode;

                inStream.Seek(0, SeekOrigin.Begin);
                CryptoStream stream = null;
                try
                {
                    ICryptoTransform transform = encAlgo.CreateDecryptor(encAlgo.Key, encAlgo.IV);
                    stream = new CryptoStream(outStream, transform, CryptoStreamMode.Write);

                    while (inStream.Read(block, 0, block.Length) > 0 && (inStream.Position < inStream.Length - len))
                    {
                        stream.Write(block, 0, block.Length);
                    }

                    outStream.Flush();
                }
                catch (Exception)
                {

                    throw;
                }
                finally
                {
                    encAlgo.Clear();
                    stream.Close();
                }
            }
            catch (Exception)
            {

                throw;
            }
            finally
            {
                inStream.Close();
                outStream.Close();
            }
        }

        //Get Name of Semmetric Algorithm
        static private string GetSymmetricAlgoName(SymmetricAlgorithm algo)
        {
            Type encType = algo.GetType();
            if (encType == typeof(RijndaelManaged) || encType == typeof(Rijndael))
            {
                return "Rijndael";
            }
            else if (encType == typeof(DES) || encType == typeof(DESCryptoServiceProvider))
            {
                return "DES";
            }
            else if (encType == typeof(TripleDES) || encType == typeof(TripleDESCryptoServiceProvider))
            {
                return "TripleDES";
            }
            else if (encType == typeof(RC2) || encType == typeof(RC2CryptoServiceProvider))
            {
                return "RC2";
            }
            return "";
        }
    }
}
